/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package hhf.flex.ascreator;

import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.util.DeclarationVisitors;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.xpath.*;
import javax.xml.xpath.XPathFactory;
import org.xml.sax.InputSource;

/**
 * 
 * @author administrateur
 */
public class Processor implements AnnotationProcessor {

	/**
	 * Environnement APT.
	 */
	private final AnnotationProcessorEnvironment env;
	/**
	 * Chemin des sources java.
	 */
	private String path;
	/**
	 * Chemin des sources as.
	 */
	private String extdir;
	private File errorsFile = null;

	/**
	 * Constructeur.
	 * @param env : environnement APT
	 * @param path : chemin des sources java
	 * @param extdir : chemin des sources as
	 */
	public Processor(final AnnotationProcessorEnvironment env, final String path, final String extdir, final String errorspath) {
		this.path = path;
		this.extdir = extdir;
		this.env = env;
		if (errorspath != null) {
			errorsFile = new File(errorspath);
		}
	}

	/**
	 * Retourne le channel définit pour les RemoteObjects.
	 * @param remoting : fichier remoting-config.xml
	 * @return : channel
	 */
	private String getChannelName(final File remoting) {
		String channel = null;
		InputStream input = null;
		try {
			// 1. Instantiate an XPathFactory.
			XPathFactory factory = XPathFactory.newInstance();
			// 2. Use the XPathFactory to create a new XPath object
			XPath xpath = factory.newXPath();
			// 3. Compile an XPath string into an XPathExpression
			XPathExpression expression = xpath.compile("//service/default-channels/channel/@ref");
			// 4. Evaluate the XPath expression on an input document
			input = new FileInputStream(remoting);
			InputSource in = new InputSource(input);
			channel = expression.evaluate(in);
		} catch (Exception ex) {
		} finally {
			if (input != null) {
				try {
					input.close();
				} catch (Exception ex) {
				}
			}
		}
		return channel;
	}

	/**
	 * Recupere le endpoint dans le fichier de services, fonction du channel.
	 * @param services : Fichier service-config.xml
	 * @param channel : channel définit pour les RemoteObject par default : my-amf.
	 * @return : endpoint
	 */
	private String getEndpoint(final File services, final String channel) {
		String endpoint = null;
		InputStream input = null;
		try {
			// 1. Instantiate an XPathFactory.
			XPathFactory factory = XPathFactory.newInstance();
			// 2. Use the XPathFactory to create a new XPath object
			XPath xpath = factory.newXPath();
			// 3. Compile an XPath string into an XPathExpression
			XPathExpression expression = xpath.compile("//services-config/channels/channel-definition[@id='" + channel + "']/endpoint/@url");
			// 4. Evaluate the XPath expression on an input document
			input = new FileInputStream(services);
			InputSource in = new InputSource(input);
			endpoint = expression.evaluate(in);
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			if (input != null) {
				try {
					input.close();
				} catch (Exception ex) {
				}
			}
		}
		return endpoint;
	}

	/**
	 * Recupere si la serialisation des collections se fait,
	 * vers un Array ou un ArrayCollection.
	 * @param services : Fichier service-config.xml
	 * @param channel : channel définit pour les RemoteObject par default : my-amf.
	 * @return : true si vers Array, false si vers ArrayCollection
	 */
	private boolean getLegacyCollection(final File services, final String channel) {
		boolean legacyCollection = false;
		InputStream input = null;
		try {
			// 1. Instantiate an XPathFactory.
			XPathFactory factory = XPathFactory.newInstance();
			// 2. Use the XPathFactory to create a new XPath object
			XPath xpath = factory.newXPath();
			// 3. Compile an XPath string into an XPathExpression
			XPathExpression expression = xpath.compile("//services-config/channels/channel-definition[@id='" + channel + "']/properties/serialization/legacy-collection");
			// 4. Evaluate the XPath expression on an input document
			input = new FileInputStream(services);
			InputSource in = new InputSource(input);
			legacyCollection = "true".equals(expression.evaluate(in));
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			if (input != null) {
				try {
					input.close();
				} catch (Exception ex) {
				}
			}
		}
		return legacyCollection;
	}

	@Override
	public void process() {
		BufferedReader in = null;
		Map<String, Object> options = new HashMap<String, Object>();
		try {
			cleanExtDir();
			copyASClassDependencies();
			Properties errorsProperties = loadErrorsBundle(errorsFile);
			options.put("errorsProperties", errorsProperties);
			File conf = new File(path, "services-config.xml");
			if (!conf.exists()) {
				Logger.getLogger(Processor.class.getName()).log(Level.SEVERE, "File : ''services-config.xml'' not found in {0}", path);
			}
			File src = new File(path, "remoting-config.xml");
			if (!src.exists()) {
				Logger.getLogger(Processor.class.getName()).log(Level.SEVERE, "File : ''remoting-config.xml'' not found in {0}", path);
			}
			String channel = getChannelName(src);
			String endpoint = getEndpoint(conf, channel);
			createBundle(endpoint);
			boolean legacyCollection = getLegacyCollection(conf, channel);
			options.put("legacy-collection", legacyCollection);
			Logger.getLogger(Processor.class.getName()).log(Level.INFO, "LEGACY-COLLECTION : {0}", legacyCollection);
			for (TypeDeclaration typeDecl : env.getSpecifiedTypeDeclarations()) {
				typeDecl.accept(DeclarationVisitors.getDeclarationScanner(new Visitor(extdir, options), DeclarationVisitors.NO_OP));
			}
			storeErrorsBundle(errorsProperties, errorsFile);
		} catch (IOException ex) {
			Logger.getLogger(Processor.class.getName()).log(Level.SEVERE, "IOError", ex);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception ex) {
				}
			}
		}
	}

	/**
	 * Supprime les fichiers generés obsoletes.
	 * @throws IOException : erreur d'IO
	 */
	private void cleanExtDir() throws IOException {
		OutputStream out = null;
		try {
			File rep = new File(extdir);
			Logger.getLogger(Processor.class.getName()).log(Level.INFO, "Cleaning directory... : {0}", extdir);
			if (rep.exists()) {
				rep.delete();
			}
			rep.mkdirs();
			File prop = new File(rep, "build.properties");
			out = new FileOutputStream(prop);
			out.write(("flex.as.generated.absdir=" + rep.getAbsolutePath().replaceAll("\\\\", "\\\\\\\\")).getBytes());
		} finally {
			if (out != null) {
				out.close();
			}
		}
	}

	/**
	 * Crée un fichier permettant de construire une localisation des erreurs.
	 * @throws IOException : erreur d'IO
	 */
	private void storeErrorsBundle(Properties errorsProperties, File props) throws IOException {
		OutputStream out = null;
		try {
			if (props != null) {
				out = new FileOutputStream(props);
				errorsProperties.store(out, null);
			}
		} finally {
			if (out != null) {
				out.close();
			}
		}
	}

	private Properties loadErrorsBundle(File propsFile) throws IOException {
		Reader reader = null;
		Properties props = new Properties();
		try {
			if (propsFile != null && propsFile.exists()) {
				reader = new FileReader(propsFile);
				props.load(reader);
			}
		} finally {
			if (reader != null) {
				reader.close();
			}
		}
		return props;
	}

	/**
	 * Crée le fichier qui servira à définir le enpoint des services.
	 * Le enpoint est lu dans le fichier services-config.xml. Normalement dans WEB-INF/flex/services-config.xml
	 * @param endpoint : url du channel du serveur cible.
	 * @throws IOException : erreur d'IO
	 */
	private void createBundle(final String endpoint) throws IOException {
		OutputStream out = null;
		try {
			File tmp = new File(extdir, "ascreator.properties");
			out = new FileOutputStream(tmp);
			URL url = new URL(endpoint);
			out.write(("AMF=" + endpoint + "\n").getBytes());
			out.write(("HOST=" + url.getHost() + "\n").getBytes());
			out.write(("PORT=" + url.getPort() + "\n").getBytes());
			Logger.getLogger(Processor.class.getName()).log(Level.INFO, "HOST : {0}", url.getHost());
			Logger.getLogger(Processor.class.getName()).log(Level.INFO, "PORT : {0}", url.getPort());
			Logger.getLogger(Processor.class.getName()).log(Level.INFO, "ENDPOINT : {0}", endpoint);
			out.close();
		} finally {
			if (out != null) {
				out.close();
			}
		}
	}

	/**
	 * Copie les dependances Flex pour la compilation.
	 * @throws IOException : erreur d'IO
	 */
	private void copyASClassDependencies() throws IOException {
		copyFile("resources", "hhf" + File.separator + "services", "AbstractROService.as");
		copyFile("resources", "hhf" + File.separator + "services" + File.separator + "events", "ServiceEvent.as");
		copyFile("resources", "hhf" + File.separator + "services" + File.separator + "methods", "AbstractMethodClass.as");
		copyFile("resources", "hhf" + File.separator + "services", "AbstractServiceResponder.as");
		copyFile("resources", "hhf" + File.separator + "net", "NetworkMonitor.as");
		copyFile("resources", "hhf" + File.separator + "services" + File.separator + "entities", "AbstractDelayedFunction.as");
		copyFile("resources", "hhf" + File.separator + "services" + File.separator + "entities", "DelayedClasses.as");
	}

	/**
	 * Copie un fichier dans le repertoire destination.
	 * @param source : fichier à copier
	 * @param dest : repertoire de sortie
	 * @throws IOException : erreur d'IO
	 */
	private void copyFile(final String repSource, final String repDest, final String filename) throws IOException {
		OutputStream out = null;
		InputStream in = null;
		File asFile = null;
		try {
			in = this.getClass().getClassLoader().getResourceAsStream(repSource + "/" + filename);
			if (in != null) {
				File rep = new File(extdir, repDest);
				if (!rep.exists()) {
					rep.mkdirs();
				}
				asFile = new File(rep, filename);
				out = new FileOutputStream(asFile);
				byte[] buffer = new byte[BUFFERSIZE];
				int read = 0;
				while ((read = in.read(buffer)) > 0) {
					out.write(buffer, 0, read);
				}
			} else {
				throw new IOException(filename + " not found");
			}
		} finally {
			try {
				if (out != null) {
					out.close();
				}
				out = null;
			} catch (IOException ioe) {
			}
			try {
				if (in != null) {
					in.close();
				}
				in = null;
			} catch (IOException ioe) {
			}
			asFile = null;
		}
	}
	/**
	 * Taille dubuffer pour copie des fichiers.
	 */
	private static final int BUFFERSIZE = 1024;
}
